use std::rc::Rc;

use crate::{expr::Operator, Env, Expr, Value};

use super::{value, EnvExt, Error};

impl Operator {
    fn apply_value_binary_op(
        env: Rc<Env>,
        exprs: &mut Vec<Expr>,
        bin_op: fn(Value, Value) -> value::Result<Value>,
    ) -> super::Result<Value> {
        let mut acc = if let Some(expr) = exprs.pop() {
            env.eval(expr)?
        } else {
            return Ok(Value::Nil);
        };

        while let Some(expr) = exprs.pop() {
            acc = bin_op(acc, env.eval(expr)?)?
        }
        Ok(acc)
    }

    fn apply_value_op(
        env: Rc<Env>,
        exprs: &mut Vec<Expr>,
        op: fn(Value) -> value::Result<Value>,
    ) -> super::Result<Value> {
        if let Some(expr) = exprs.pop() {
            Ok(op(env.eval(expr)?)?)
        } else {
            Ok(Value::Nil)
        }
    }

    fn apply_value_cmp(
        env: Rc<Env>,
        exprs: &mut Vec<Expr>,
        cmp: fn(Value, Value) -> value::Result<Value>,
    ) -> super::Result<Value> {
        let first = if let Some(expr) = exprs.pop() {
            env.eval(expr)?
        } else {
            return Ok(Value::Nil);
        };

        let second = if let Some(expr) = exprs.pop() {
            env.eval(expr)?
        } else {
            return Ok(Value::Nil);
        };

        Ok(cmp(first, second)?)
    }

    pub fn apply(&self, env: Rc<Env>, mut exprs: Vec<Expr>) -> super::Result<Value> {
        match self {
            Self::Err => Err(Error::Custom(
                env.eval(exprs.pop().unwrap_or(Expr::Nil))?.to_string(),
            )),
            Self::Cons => {
                let left = exprs
                    .pop()
                    .ok_or(Error::TooFewArguments {
                        func: "cons",
                        needs: 2,
                    })
                    .and_then(|expr| env.eval(expr))?;

                let right = exprs
                    .pop()
                    .ok_or(Error::TooFewArguments {
                        func: "cons",
                        needs: 2,
                    })
                    .and_then(|expr| env.eval(expr))?;

                match right {
                    Value::Nil => Ok(Value::List(vec![left])),
                    Value::List(mut l) => {
                        l.push(left);
                        Ok(Value::List(l))
                    }
                    _ => Ok(Value::List(vec![right, left]))
                }
            }
            Self::Sub => {
                let mut acc = exprs
                    .pop()
                    .ok_or(Error::TooFewArguments {
                        func: "cons",
                        needs: 2,
                    })
                    .and_then(|expr| env.eval(expr))?;

                if exprs.is_empty() {
                    return Ok(acc.neg()?);
                }

                while let Some(expr) = exprs.pop() {
                    acc = acc.op_sub(env.eval(expr)?)?
                }
                Ok(acc)
            }
            Self::At => {
                let list = exprs
                    .pop()
                    .ok_or(Error::TooFewArguments {
                        func: "@",
                        needs: 2,
                    })
                    .and_then(|expr| env.eval(expr))?;

                let idx = exprs
                    .pop()
                    .ok_or(Error::TooFewArguments {
                        func: "@",
                        needs: 2,
                    })
                    .and_then(|expr| env.eval(expr))?;

                Ok(list.at(idx)?)
            }
            Self::If => {
                let cond = exprs
                    .pop()
                    .ok_or(Error::TooFewArguments {
                        func: "if",
                        needs: 3,
                    })
                    .and_then(|expr| env.eval(expr))?
                    .is_true();

                if cond {
                    exprs
                        .pop()
                        .ok_or(Error::TooFewArguments {
                            func: "if",
                            needs: 3,
                        })
                        .and_then(|expr| env.eval(expr))
                } else {
                    let _ = exprs.pop();
                    exprs
                        .pop()
                        .ok_or(Error::TooFewArguments {
                            func: "if",
                            needs: 3,
                        })
                        .and_then(|expr| env.eval(expr))
                }
            }
            Self::Add => Self::apply_value_binary_op(env, &mut exprs, Value::op_add),
            Self::Mul => Self::apply_value_binary_op(env, &mut exprs, Value::op_mul),
            Self::Div => Self::apply_value_binary_op(env, &mut exprs, Value::op_div),
            Self::Shl => Self::apply_value_binary_op(env, &mut exprs, Value::op_shl),
            Self::Shr => Self::apply_value_binary_op(env, &mut exprs, Value::op_shr),
            Self::BitAnd => Self::apply_value_binary_op(env, &mut exprs, Value::op_bitand),
            Self::BitOr => Self::apply_value_binary_op(env, &mut exprs, Value::or),
            Self::And => Self::apply_value_binary_op(env, &mut exprs, Value::and),
            Self::Or => Self::apply_value_binary_op(env, &mut exprs, Value::or),
            Self::BitNot => Self::apply_value_op(env, &mut exprs, Value::op_bitnot),
            Self::Not => Self::apply_value_op(env, &mut exprs, Value::op_not),
            Self::Car => Self::apply_value_op(env, &mut exprs, Value::car),
            Self::Cdr => Self::apply_value_op(env, &mut exprs, Value::cdr),
            Self::Eq => Self::apply_value_cmp(env, &mut exprs, Value::eq),
            Self::LessThan => Self::apply_value_cmp(env, &mut exprs, Value::lt),
            Self::GreaterThan => Self::apply_value_cmp(env, &mut exprs, Value::gt),
            Self::LessEq => Self::apply_value_cmp(env, &mut exprs, Value::le),
            Self::GreaterEq => Self::apply_value_cmp(env, &mut exprs, Value::ge),
            Self::Eval => {
                match exprs
                    .pop()
                    .ok_or(Error::TooFewArguments {
                        func: "eval",
                        needs: 1,
                    })
                    .and_then(|expr| env.eval(expr))?
                {
                    Value::Expr(expr) => Ok(env.eval(expr)?),
                    expr => Err(value::Error::TypeMismatch {
                        expect: Value::TYPE_NAME_EXPR,
                        get: expr.type_name(),
                    })?,
                }
            }
        }
    }
}
